/*
 * Copyright (c) 2008 the original author or authors.
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */

package etch.tools.maven;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;

import etch.compiler.EtchMain;

/**
 * Compiles etch files (<code>*.etch</code>) to source files of a variety of
 * programming languages. Detailed information about the Etch compiler options
 * can be found on the <a href=
 * "http://developer.cisco.com/web/cuae/wikidocs?src=/wiki/display/CUAE/Etch"
 * >Etch web site</a>.
 * 
 * @goal etch
 * @phase generate-sources
 * @author Jonhnny Weslley
 */
public class EtchMojo extends AbstractMojo {

	/**
	 * The current Maven project.
	 * 
	 * @parameter expression="${project}"
	 * @required
	 * @readonly
	 */
	private MavenProject project;

	/**
	 * Specifies a target language binding to generate.
	 * 
	 * @required
	 * @parameter expression="${etch.binding}" default-value="java"
	 */
	private String binding;

	/**
	 * Specifies what files (binding-specific) to generate; separated with "
	 * <code>,;: </code>".
	 * 
	 * @parameter expression="${etch.what}" default-value="all"
	 */
	private String what;

	/**
	 * The directory where the etch files (<code>*.etch</code>) are located.
	 * 
	 * @parameter expression="${etch.sourceDirectory}"
	 *            default-value="${basedir}/src/main/etch"
	 */
	private File sourceDirectory;

	/**
	 * The directory where the output generated files will be located. The
	 * directory will be registered as a compile source root of the project such
	 * that the generated files will participate in later build phases like
	 * compiling and packaging.
	 * 
	 * @parameter expression="${etch.outputDirectory}"
	 *            default-value="${project.build.directory}/generated-sources/etch"
	 */
	private File outputDirectory;

	/**
	 * The directory where the output generated files for mix-ins will be
	 * located. The directory will be registered as a compile source root of the
	 * project such that the generated files will participate in later build
	 * phases like compiling and packaging.
	 * 
	 * @parameter expression="${etch.mixinOutputDirectory}"
	 */
	private File mixinOutputDirectory;

	/**
	 * The directory where the output generated user editable template files
	 * will be located. The directory will be registered as a compile source
	 * root of the project such that the generated files will participate in
	 * later build phases like compiling and packaging.
	 * 
	 * @parameter expression="${etch.templateOutputDirectory}"
	 */
	private File templateOutputDirectory;

	/**
	 * Etch source file to compile.
	 * 
	 * @required
	 * @parameter expression="${file}"
	 */
	private File sourceFile;

	/**
	 * File name of a user-specified reserved words list.
	 * 
	 * @parameter expression="${etch.userWordsList}"
	 */
	private File userWordsList;

	/**
	 * If <code>false</code>, just report problems.
	 * 
	 * @parameter expression="${etch.verbose}" default-value="false"
	 */
	private boolean verbose;

	/**
	 * If <code>true</code>, mix-in artifacts should not be generated.
	 * 
	 * @parameter expression="${etch.noMixinArtifacts}" default-value="false"
	 */
	private boolean noMixinArtifacts;

	/**
	 * If <code>true</code>, namespace directory tree should not be flattened.
	 * 
	 * @parameter expression="${etch.noFlattenPackages}" default-value="false"
	 */
	private boolean noFlattenPackages;

	/**
	 * If <code>true</code>, ignore the global reserved words list.
	 * 
	 * @parameter expression="${etch.ignoreGlobalWordsList}" default-value="false"
	 */
	private boolean ignoreGlobalWordsList;

	/**
	 * If <code>true</code>, ignore the local (binding-specific) reserved words
	 * list.
	 * 
	 * @parameter expression="${etch.ignoreLocalWordsList}" default-value="false"
	 */
	private boolean ignoreLocalWordsList;

	/**
	 * If <code>true</code>, ignore the ETCH_INCLUDE_PATH environment variable.
	 * 
	 * @parameter expression="${etch.ignoreIncludePath}" default-value="false"
	 */
	private boolean ignoreIncludePath;

	/**
	 * Option to print the command-line used in compiling.
	 * 
	 * @parameter expression="${etch.displayCommand}"
	 *            default-value="false"
	 */
	private boolean displayCommand;

	/**
	 * Compiles the etch files(<code>*.etch</code>).
	 */
	public final void execute() throws MojoExecutionException {
		try {
			(new EtchMain()).doMain(getCommandArguments());

		} catch (Exception e) {
			throw new MojoExecutionException("wrap: " + e, e);
		}
	}

	/**
	 * Gets the command-line arguments to the Etch's compiler tool.
	 * 
	 * @return The command-line arguments to the Etch's compiler tool, never <code>null</code>.
	 */
	private String[] getCommandArguments() {
		Arguments arguments = new Arguments();

		arguments
			.add("--quiet", !verbose)
			.add("--no-mixin-artifacts", noMixinArtifacts)
			.add("--no-flatten-packages", noFlattenPackages)
			.add("--ignore-global-words-list", ignoreGlobalWordsList)
			.add("--ignore-local-words-list", ignoreLocalWordsList)
			.add("--ignore-include-path", ignoreIncludePath)

			.add("--binding", binding)
			.add("--what", what)

			.add("--output-dir", outputDirectory)
			.add("--include-path", sourceDirectory)
			.add("--mixin-output-dir", mixinOutputDirectory)
			.add("--template-output-dir", templateOutputDirectory)
			.add("--user-words-list", userWordsList)
			.add(sourceFile)
		;

		if (displayCommand) {
			getLog().info(String.format("etch %s", arguments));
		}

		addCompileSourceRoot(outputDirectory);
		addCompileSourceRoot(mixinOutputDirectory);
		addCompileSourceRoot(templateOutputDirectory);
		return arguments.get();
	}

	/**
	 * Registers the specified directory as a compile source root for the
	 * current project.
	 * 
	 * @param directory
	 *            The absolute path to the compile source, must not be
	 *            <code>null</code>.
	 */
	private void addCompileSourceRoot(File directory) {
		if (project != null && directory != null) {
			getLog().debug("Adding compile source root: " + directory);
			this.project.addCompileSourceRoot(directory.getAbsolutePath());
		}
	}


	static class Arguments {

		private final List<String> args = new ArrayList<String>();

		Arguments add(File value) {
			if (value != null) {
				args.add(value.getAbsolutePath());
			}
			return this;
		}

		Arguments add(String name, boolean value) {
			if (value) {
				args.add(name);
			}
			return this;
		}

		Arguments add(String name, String value) {
			if (!StringUtils.isEmpty(value)) {
				args.add(name);
				args.add(value);
			}
			return this;
		}

		Arguments add(String name, File value) {
			if (value != null) {
				args.add(name);
				args.add(value.getAbsolutePath());
			}
			return this;
		}

		String[] get() {
			return args.toArray(new String[args.size()]);
		}

		@Override
		public String toString() {
			return StringUtils.join(args.iterator(), " ");
		}

	}

}
